﻿using System;
using System.Globalization;
using System.IO;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using AuthoritExtensibility;

// The Authorit.API.Utilities namespace contains classes that help Author-it plug-in developers
// with common processes such as getting library configuration information, application context,
// and word counts.
[assembly: SecurityPermission(SecurityAction.RequestMinimum, Execution = true)]
[assembly: CLSCompliant(true)]

namespace Authorit.API.Configuration
{
	/// <summary>
	/// Specifies plug-in button image size
	/// </summary>
	/// <seealso cref="ToolbarAddArgs.IconImage"/>
	public enum ButtonSize
	{
		/// <summary>
		/// No button image
		/// </summary>
		None = 0,

		/// <summary>
		/// Image is 16 pixels x 16 pixels
		/// </summary>
		Small = 16,

		/// <summary>
		/// Image is 32 pixels x 32 pixels
		/// </summary>
		Large = 32,
	}

	/// <summary>
	/// Specifies the context from which the user launched the plug-in
	/// </summary>
	/// <seealso cref="ToolbarAddArgs.OnClick"/>
	/// <seealso cref="ContextMenuAddArgs.OnClick"/>
	/// <seealso cref="TextEditorAddArgs.OnClick"/>
	[FlagsAttribute]
	public enum LaunchPoints
	{
		/// <summary>
		/// Not launched by the user
		/// </summary>
		/// <remarks>Use this value for event-based plug-ins.</remarks>
		None = 0,

		/// <summary>
		/// Launched from the book editor toolbar
		/// </summary>
		BookEditor = 1,

		/// <summary>
		/// Launched from the context menu in a folder or search results view
		/// </summary>
		MainContextMenu = 2,

		/// <summary>
		/// Launched from the main toolbar
		/// </summary>
		MainToolBar = 4,

		/// <summary>
		/// Launched from a topic in the text editor
		/// </summary>
		TextEditor = 8
	}

	/// <summary>
	/// Specifies the type of Author-it library.
	/// </summary>
	/// <seealso cref="PluginUtility.GetLibraryConfiguration(AuthoritEventArgs)"/>
	/// <seealso cref="PluginUtility.GetLibraryConfigurationList(AuthoritEventArgs)"/>
	/// <seealso cref="PluginUtility.GetLibraryType(AuthoritEventArgs)"/>
	/// <seealso cref="Authorit.API.ApplicationContext"/>
	public enum LibraryType
	{
		#region Enumeration values

		#region Unknown = 0

		/// <summary>
		/// Unknown or invalid library information
		/// </summary>
		Unknown = 0,

		#endregion Unknown = 0

		#region Jet = 1

		/// <summary>
		/// Jet file library
		/// </summary>
		Jet = 1,

		#endregion Jet = 1

		#region Sql = 2

		/// <summary>
		/// SQL Server library
		/// </summary>
		Sql = 2

		#endregion Sql = 2

		#endregion Enumeration values
	}

	/// <summary>
	/// Provides methods that return information about an Author-it library.
	/// </summary>
	public static class PluginUtility
	{
		#region Fields (1) 

		/// <summary>
		/// Stores a compiled regular expression used for
		/// removing HTML tags from text.
		/// </summary>
		/// <seealso cref="GetTopicText(int, Authorit.API.Services.Topic)"/>
		/// <seealso cref="RegexOptions.Compiled"/>
		/// <seealso cref="Regex(string, RegexOptions)"/>
		/// <value>Regex("&lt;.*?>", RegexOptions.Compiled)</value>
		private static Regex htmlRegex = new Regex("<.*?>", RegexOptions.Compiled);

		#endregion Fields 

		#region Methods (14) 

		// Public Methods (13) 

		/// <summary>
		/// Gets an instance of the <see cref="Authorit.API.ApplicationContext"/> class.
		/// </summary>
		/// <param name="args">The instance of the <see cref="AuthoritExtensibility.AuthoritEventArgs"/> class to use for the context</param>
		/// <returns>Application context for the <paramref name="args"/></returns>
        /// <seealso cref="GetLibraryConfigurationList(AuthoritEventArgs)"/>
		/// <seealso cref="ApplicationContext(LibraryConfigurationList)"/>
		public static Authorit.API.ApplicationContext GetAppContext(AuthoritEventArgs args)
		{
			Authorit.API.ApplicationContext appContext = new ApplicationContext(GetLibraryConfigurationList(args));
			return appContext;
		}

		/// <summary>
		/// Gets the name of the library database.
		/// </summary>
		/// <param name="args">Arguments passed to the plug-in from Author-it</param>
		/// <returns>Name of the database</returns>
		/// <seealso cref="GetLibraryType(AuthoritEventArgs)"/>
		public static string GetDatabaseName(AuthoritEventArgs args)
		{
			string dbName = String.Empty;  //Initialize the database name
			switch (GetLibraryType(args))  //Get the type of database that is hosting the library
			{
				case LibraryType.Unknown:  //Cannot database type
					break;  //Exit the switch

				case LibraryType.Jet:  //Jet database
					dbName = args.LibraryPath;  //Set the database name to the path to the Jet database file
					break;  //Exit the switch

				case LibraryType.Sql:  //SQL Server database
					dbName = args.SqlDatabase;  //Set the database name to the name of the SQL Server database
					break;  //Exit the switch

				default:
					break;
			}
			return dbName;  //Return the database name
		}

		/// <summary>
		/// Gets a Jet library configuration.
		/// </summary>
		/// <param name="args">An instance of the <see cref="AuthoritEventArgs"/> class.</param>
		/// <returns>An instance of the <see cref="Authorit.API.Configuration.JetLibraryConfiguration"/> class for the Jet library specified by the <paramref name="args"/> parameter.</returns>
		/// <exception cref="ArgumentException">Thrown when the <see cref="AuthoritEventArgs.LibraryPath"/> property of the <paramref name="args"/> parameter is <value>null</value></exception>
		/// <exception cref="FileNotFoundException">Thrown when the jet library specified in the <see cref="AuthoritEventArgs.LibraryPath"/> property of the <paramref name="args"/> parameter does not exist</exception>
		/// <seealso cref="GetLibraryConfigurationList(AuthoritEventArgs)"/>
		/// <seealso cref="GetLibraryType(AuthoritEventArgs)"/>
		/// <remarks>Use the <see cref="GetJetLibraryConfiguration(AuthoritEventArgs)"/> method when you know the library is a Jet library.</remarks>
		public static API.Configuration.JetLibraryConfiguration GetJetLibraryConfiguration(AuthoritEventArgs args)
		{
			if (null == args.LibraryPath)
			{
				throw new ArgumentNullException("args");
			}
			Configuration.JetLibraryConfiguration jetConfig = new Authorit.API.Configuration.JetLibraryConfiguration();

			System.IO.FileInfo jetFileInfo = new FileInfo(args.LibraryPath);
			if (jetFileInfo.Exists)
			{
				jetConfig.Alias = jetFileInfo.Name;
				jetConfig.Path = jetFileInfo.FullName;
			}
			else
			{
				string notFoundMessage = String.Format(CultureInfo.CurrentCulture, Properties.Resources.ExceptionMessageFileNotFound, jetFileInfo.Name, jetFileInfo.DirectoryName);
				throw new FileNotFoundException(notFoundMessage, jetFileInfo.FullName);
			}
			return jetConfig;
		}

		/// <summary>
		/// Gets a new library configuration that contains information for the specified library connection.
		/// </summary>
		/// <param name="args">An instance of the <see cref="AuthoritEventArgs"/> class that contains library connection information</param>
		/// <returns>A new instance of the <see cref="API.Configuration.LibraryConfigurationBase"/> class containing library information specified by the <paramref name="args"/></returns>
		/// <remarks>The <see cref="GetLibraryConfiguration(AuthoritEventArgs)"/> method returns <value>null</value> if the method cannot determine the library type.</remarks>
		/// <seealso cref="GetLibraryType(AuthoritEventArgs)"/>
		public static API.Configuration.LibraryConfigurationBase GetLibraryConfiguration(AuthoritEventArgs args)
		{
			API.Configuration.LibraryConfigurationBase libConfig;
			libConfig = null;
			switch (GetLibraryType(args))
			{
				case LibraryType.Unknown:
					break;

				case LibraryType.Jet:
					libConfig = GetJetLibraryConfiguration(args);
					break;

				case LibraryType.Sql:
					libConfig = GetSqlLibraryConfiguration(args);
					break;

				default:
					break;
			}
			return libConfig;
		}

		/// <summary>
		/// Gets a new library configuration list that contains information for the specified library connection.
		/// </summary>
		/// <param name="args">An instance of the <see cref="AuthoritEventArgs"/> class that contains library connection information</param>
		/// <returns>A new instance of the <see cref="API.Configuration.LibraryConfigurationList"/> class containing library information specified by the <paramref name="args"/> parameter</returns>
		/// <remarks>Use the <see cref="GetLibraryConfigurationList(AuthoritEventArgs)"/> method to get a new instance of the <see cref="API.Configuration.LibraryConfigurationList"/> class with one member.
		/// To add members to an existing instance of <see cref="API.Configuration.LibraryConfigurationList"/> use the <see cref="GetLibraryConfiguration(AuthoritEventArgs)"/> method and add the return value.</remarks>
		/// <seealso cref="GetLibraryType(AuthoritEventArgs)"/>
		public static API.Configuration.LibraryConfigurationList GetLibraryConfigurationList(AuthoritEventArgs args)
		{
			API.Configuration.LibraryConfigurationList libConfigList = new Authorit.API.Configuration.LibraryConfigurationList();
			switch (GetLibraryType(args))
			{
				case LibraryType.Unknown:
					break;

				case LibraryType.Jet:
					libConfigList.Add(GetJetLibraryConfiguration(args));
					break;

				case LibraryType.Sql:
					libConfigList.Add(GetSqlLibraryConfiguration(args));
					break;

				default:
					break;
			}
			return libConfigList;
		}

		/// <summary>
		/// Gets the type of Author-it library specified by the <paramref name="args"/> parameter.
		/// </summary>
		/// <param name="args">An instance of the <see cref="AuthoritEventArgs"/> class that contains Author-it library information</param>
		/// <returns>One of the valid <see cref="LibraryType"/> values</returns>
		/// <remarks>
		/// The method returns <see cref="LibraryType.Unknown"/> when the <paramref name="args"/> properties contain
		/// values for both <see cref="LibraryType.Jet"/> and <see cref="LibraryType.Sql"/> types
		/// or if the parameter contains null values for both types.
		/// </remarks>
		/// <seealso cref="GetLibraryConfigurationList(AuthoritEventArgs)"/>
		/// <seealso cref="GetLibraryType(AuthoritEventArgs)"/>
		public static LibraryType GetLibraryType(AuthoritEventArgs args)
		{
			if (null == args.LibraryPath && (null == args.SqlServername || null == args.SqlDatabase))  //Are the database properties null?
			{
				return LibraryType.Unknown;  //Cannot determine the library type
			}
			else if (null != args.LibraryPath && (null != args.SqlServername || null != args.SqlServername))  //Do the Jet and SQL Server properties have values?
			{
				return LibraryType.Unknown;  //Cannot determine the library type
			}
			else if (null != args.LibraryPath)  //Does the library path property have a value?
			{
				return LibraryType.Jet;  //It is a Jet library
			}
			else if (null != args.SqlServername && null != args.SqlDatabase)  //Do the SQL Server properties have values?
			{
				return LibraryType.Sql;  //It is a SQL library
			}
			return LibraryType.Unknown;  //Cannot determine the library type if we get this far
		}

		/// <summary>
		/// Gets a SQL Server library configuration using the specified arguments.
		/// </summary>
		/// <param name="args">The arguments for the event</param>
		/// <returns>A SQL Server library configuration</returns>
		/// <remarks>Use the <see cref="GetJetLibraryConfiguration(AuthoritEventArgs)"/> method when you know the library specified by <paramref name="args"/> is a <see cref="LibraryType.Sql"/> library. Use the <see cref="GetLibraryType(AuthoritEventArgs)"/> method to determine the library type.</remarks>
		public static API.Configuration.SqlServerLibraryConfiguration GetSqlLibraryConfiguration(AuthoritEventArgs args)
		{
			ValidateParameter(args, "args");
			Configuration.SqlServerLibraryConfiguration sqlConfig = new Authorit.API.Configuration.SqlServerLibraryConfiguration();
			if (null == args.SqlDatabase || null == args.SqlServername)
			{
				throw new ArgumentException(Properties.Resources.ExceptionMessageSqlPropertiesNull, "args");
			}

			sqlConfig.Database = args.SqlDatabase;
			sqlConfig.Server = args.SqlServername;
			sqlConfig.Alias = String.Format(CultureInfo.CurrentCulture, Properties.Resources.LibraryAliasSql, args.SqlDatabase, args.SqlServername);

			if (!IsTrustedSqlConnection(args))
			{
				sqlConfig.TrustedConnection = false;
				sqlConfig.LoginID = args.SqlUsername;
				sqlConfig.Password = args.SqlPassword;
			}
			else
			{
				sqlConfig.TrustedConnection = true;
			}

			return sqlConfig;
		}

		/// <summary>
		/// Gets the content of the specified text with the markup removed.
		/// </summary>
		/// <param name="topicTextXml">String containing the topic text XML</param>
		/// <returns>String containing the topic text with XML/HTML markup removed</returns>
		/// <seealso cref="htmlRegex"/>
		public static string GetTopicText(string topicTextXml)
		{
			return htmlRegex.Replace(topicTextXml, " ");
		}

		/// <summary>
		/// Gets the text of the specified topic.
		/// </summary>
		/// <param name="topicId">The object ID of the topic</param>
		/// <param name="topicService">The topic service to use</param>
		/// <returns>A string containing the text of the specified topic with HTML tags removed</returns>
		/// <seealso cref="GetTopicTextHtml(int, API.Services.Topic)"/>
		/// <seealso cref="htmlRegex"/>
		/// <seealso cref="Regex.Replace(string, string)"/>
		public static string GetTopicText(int topicId, API.Services.Topic topicService)
		{
			return htmlRegex.Replace(GetTopicTextHtml(topicId, topicService), " ");
		}

		/// <summary>
		/// Gets the HTML text of the specified topic.
		/// </summary>
		/// <param name="topicId">The object ID of the topic</param>
		/// <param name="topicService">The topic service to use</param>
		/// <returns>A string containing the HTML text of the specified topic</returns>
		/// <seealso cref="Services.Topic.GetTopicTextXml(int)"/>
		public static string GetTopicTextHtml(int topicId, API.Services.Topic topicService)
		{
			string topicText = "";
			string xml = topicService.GetTopicTextXml(topicId);
			if (!String.IsNullOrEmpty(xml))
			{
				topicText = xml;
			}
			return topicText;
		}

		/// <summary>
		/// Gets the count of words in the specified string.
		/// </summary>
		/// <param name="text">The string containing words to count</param>
		/// <returns>The number of words in the specified string</returns>
		/// <seealso cref="MatchCollection.Count"/>
		/// <seealso cref="Regex.Matches(string, string)"/>
		public static int GetWordCount(string text)
		{
			//TODO: Implement GetWordCount(string, string) so you can use your own expression
			MatchCollection collection = Regex.Matches(text, @"[\S]+");
			return collection.Count;
		}

		/// <summary>
		/// Gets the count of words in the specified topic.
		/// </summary>
		/// <param name="topicId">The object ID of the topic</param>
		/// <param name="topicService">The topic service to use</param>
		/// <returns>The number of words</returns>
		/// <seealso cref="GetWordCount(string)"/>
		/// <seealso cref="GetTopicText(int, API.Services.Topic)"/>
		public static int GetWordCount(int topicId, API.Services.Topic topicService)
		{
			return GetWordCount(GetTopicText(topicId, topicService));
		}

		/// <summary>
		/// Indicates whether the specified SQL Server connection uses a trusted connection.
		/// </summary>
		/// <param name="args">An instance of the <see cref="AuthoritEventArgs"/> class that contains SQL Server connection information</param>
		/// <returns>true if the information specifies a trusted connection; otherwise false</returns>
        /// <seealso cref="ValidateParameter(object, string)"/>
		public static bool IsTrustedSqlConnection(AuthoritEventArgs args)
		{
			ValidateParameter(args, "args");
			bool trusted = false;

			if (null != args.SqlServername && null != args.SqlDatabase && null == args.SqlUsername)
			{
				trusted = true;
			}

			return trusted;
		}
		// Private Methods (1) 

		/// <summary>
		/// Validates whether an object is not null.
		/// </summary>
		/// <param name="param">The parameter to validate</param>
		/// <param name="paramName">The name of the parameter</param>
		/// <remarks>The <see cref="PluginUtility"/> class uses the <see cref="ValidateParameter(object, string)"/> method
		/// to validate parameters passed to public methods.</remarks>
		/// <exception cref="ArgumentNullException">Thrown if the specified parameter is null.</exception>
		private static void ValidateParameter(object param, string paramName)
		{
			if (null == param)
			{
				throw new ArgumentNullException(paramName);
			}
		}

		#endregion Methods 
	}
}